
`include "common_header.verilog"

//  *************************************************************************
//  File : top_mdio_xl
//  *************************************************************************
//  This program is controlled by a written license agreement.
//  Unauthorized reproduction or use is expressly prohibited. 
//  Copyright (c) 2014 Morethanip, 85757 Karlsfeld, Germany
//  info@morethanip.com
//  http://www.morethanip.com
//  *************************************************************************
//  Designed by : Daniel Koehler
//  info@morethanip.com
//  *************************************************************************
//  Description: MDIO Master Controller Top Level for polled mdio controller.
//               Using the shifter submodule enabling read/write of the
//               control registers without any latency.
//  Version    : $Id: top_mdio_xl.v,v 1.2 2015/01/26 09:42:35 dk Exp $
//  *************************************************************************

module top_mdio_xl (

   reset,
   clk,
   mdc,
   mdio_in,
   mdio_out,
   mdio_oen,
   mdio_data_in,
   mdio_data_out,
   mdio_busy_trans,
   mdio_addr,
   mdio_rd,
   mdio_wr,
   mdio_busy);

parameter MDIO_CLK_DIV = 9'd 40;//  Host Clock Division - MDC Generation

input   reset;                  //  Active High Master Reset
input   clk;                    //  System Clock
output  mdc;                    //  2.5MHz Interface
input   mdio_in;                //  Data Bus In
output  mdio_out;               //  Data Bus Out
output  mdio_oen;               //  Data Bus Output Enable      
input   [15:0] mdio_data_in;    //  Data to MDIO module
output  [15:0] mdio_data_out;   //  Data from MDIO module
output  mdio_busy_trans;        //  ongoing MDIO transaction indication
input   [1:0] mdio_addr;        //  MDIO register address
input   mdio_rd;                //  Read Enable
input   mdio_wr;                //  Write Enable
output  mdio_busy;              //  Local register R/W transaction                                 

wire    mdc; 
reg     mdio_out; 
reg     mdio_oen; 
wire    [15:0] mdio_data_out; 
wire    mdio_busy_trans; 
reg     mdio_busy; 

wire    mdc_int; 
wire    clk_ena; 
wire    mdio_out_int; 
wire    mdio_oen_int; 
wire    busy_trans_int; 

//  two local registers to set the clock divisor and the hold time in reg_clk cycles
reg     [8:0] reg_divisor; //  clock divisor
reg     [2:0] reg_holdtime; //  output hold time in reg_clk cycles
reg     reg_ena45;              // enable clause45 mode
//  hold time pipeline
reg     [13:0] mdio_out_s; 
reg     [13:0] mdio_oen_s; 

reg     mdio_wr_r;
wire    reg_wr;                 // write pulse

// shifter
wire    [31:0]  reg_data_in;    // data to write
reg             mmfr_wr;        // write data into mmfr
wire    [31:0]  reg_mmfr;       // the shift register contents currently
reg             dis_pre;        // disable preamble
reg             startt;         // start trigger
reg             read_t;         // read transaction is requested
reg             read_p;         // read transaction is requested with post increment
reg     [1:0]   op_code;        // transaction opcode clause 22/45 specific
wire            mdio_busy_int;  // transaction running indicator

mdio_clk_genc U_CLKG (

          .reset(reset),
          .clk(clk),
          .divisor(reg_divisor),
          .clk_ena(clk_ena),
          .mdio_clk(mdc_int));
          
assign mdc = mdc_int; 

// -----------------
// frame serializer
// -----------------
module_mdio_shift U_MSFT(

        .reset_pclk(     reset),          // async active high
        .pclk(           clk),            // register clock
        .clk_ena(        clk_ena),        // clock enable 
        .reg_data_in(    reg_data_in),    // data to write
        .mmfr_wr(        mmfr_wr),        // write data into mmfr
        .reg_mmfr(       reg_mmfr),       // the register contents currently
        .dis_pre(        dis_pre),        // disable preamble
        .startt(         startt),         // start trigger
        .mdio_busy(      mdio_busy_int),      // busy indicator
        .mdio_in(        mdio_in),
        .mdio_out(       mdio_out_int),
        .mdio_oen(       mdio_oen_int) );        

//  Delay from MDC Clock (programmable hold time)
//  ---------------------------------------------

always @(posedge reset or posedge clk)
   begin : process_1
   if (reset == 1'b 1)
      begin
      mdio_out_s <= 14'd 0;
      mdio_oen_s <= 14'd 0;	
      mdio_out   <= 1'b 0;	
      mdio_oen   <= 1'b 1;	
      end
   else
      begin
      mdio_out_s <= {mdio_out_s[12:0], mdio_out_int};	
      mdio_oen_s <= {mdio_oen_s[12:0], mdio_oen_int};	
      case (reg_holdtime)
      3'b 000:
         begin
         mdio_out <= mdio_out_int;	
         mdio_oen <= mdio_oen_int;	
         end
      3'b 001:
         begin
         mdio_out <= mdio_out_s[1];	
         mdio_oen <= mdio_oen_s[1];	
         end
      3'b 010:
         begin
         mdio_out <= mdio_out_s[3];	
         mdio_oen <= mdio_oen_s[3];	
         end
      3'b 011:
         begin
         mdio_out <= mdio_out_s[5];	
         mdio_oen <= mdio_oen_s[5];	
         end
      3'b 100:
         begin
         mdio_out <= mdio_out_s[7];	
         mdio_oen <= mdio_oen_s[7];	
         end
      3'b 101:
         begin
         mdio_out <= mdio_out_s[9];	
         mdio_oen <= mdio_oen_s[9];	
         end
      3'b 110:
         begin
         mdio_out <= mdio_out_s[11];	
         mdio_oen <= mdio_oen_s[11];	
         end
      3'b 111:
         begin
         mdio_out <= mdio_out_s[13];	
         mdio_oen <= mdio_oen_s[13];	
         end
      default:
         begin
         mdio_out <= mdio_out_int;	
         mdio_oen <= mdio_oen_int;	
         end
      endcase
      end
   end


//  Local Module Register I/O control
//  ---------------------------------

// create write pulse

always @(posedge reset or posedge clk)
begin : pwrp
        if (reset == 1'b 1)
        begin
                mdio_wr_r <= 1'b 0;
        end
        else
        begin
                mdio_wr_r <= mdio_wr;
        end      
end

assign reg_wr = mdio_wr & ~mdio_wr_r;   // rising edge


always @(posedge reset or posedge clk)
   begin : process_2
   if (reset == 1'b 1)
      begin
      mdio_busy <= 1'b 1;   
      mmfr_wr   <= 1'b 0;
      startt    <= 1'b 0;
      read_t    <= 1'b 0;
      read_p    <= 1'b 0;
      op_code   <= 2'b 00;
      end
   else
      begin

        //  immediately deassert busy following a read/write
        //  ------------------------------------------------
   
        if (mdio_rd == 1'b 1 | mdio_wr==1'b 1)
        begin
                mdio_busy <= 1'b 0;   
        end
        else
        begin
                mdio_busy <= 1'b 1;   
        end

        //  write command to shifter
        //  ------------------------
      
        if (reg_wr == 1'b 1 & (mdio_addr==2'b 01 | mdio_addr==2'b 10 | mdio_addr==2'b 11) )
        begin
                mmfr_wr <= 1'b 1;
        end
        else
        begin
                mmfr_wr <= 1'b 0;
        end
        
        //  transaction start control
        //  -------------------------
        if (reg_wr == 1'b 1 & ((mdio_addr==2'b 01 & mdio_data_in[15:14]!=2'b 00) |  // read transaction start with command bit
                               (mdio_addr==2'b 10) |                                // write transaction start by writing data register
                               (mdio_addr==2'b 11)) )                               // write transaction start by writing address register
        begin
                startt <= 1'b 1;
                
                if(mdio_addr==2'b 01)
                begin
                        read_t <= mdio_data_in[15];     // indicate if it is a read transaction
                        read_p <= mdio_data_in[14];     // indicate if it is a read transaction
                end
                else
                begin
                        read_t <= 1'b 0;
                        read_p <= 1'b 0;
                end
                
                // create opcode accordingly
                if( reg_ena45==1'b 0 )
                begin
                        // clause 22 transactions
                        if( mdio_addr==2'b 01 & mdio_data_in[15]==1'b 1 ) 
                        begin
                                op_code <= 2'b 10;      // read
                        end
                        else
                        begin
                                op_code <= 2'b 01;      // write
                        end

                end
                else
                begin
                        // clause 45 transactions
                        if( mdio_addr==2'b 01 & mdio_data_in[15]==1'b 1 ) 
                        begin
                                op_code <= 2'b 11;      // read
                        end
                        else if( mdio_addr==2'b 01 & mdio_data_in[14]==1'b 1 )
                        begin
                                op_code <= 2'b 10;      // read, post-inc
                        end
                        else if( mdio_addr==2'b 11 )
                        begin
                                op_code <= 2'b 00;      // address
                        end
                        else
                        begin
                                op_code <= 2'b 01;      // write
                        end
                        
                end
                
        end
        else if( mdio_busy_int==1'b 1 )
        begin
                // clear when statemachine started producing the frame
                startt <= 1'b 0;
                read_t <= 1'b 0;
                read_p <= 1'b 0;
                op_code <= 2'b 01;      // write
        end

      end
   end

// indicate transaction busy
// -------------------------
assign busy_trans_int = startt | mdio_busy_int;

assign mdio_busy_trans = busy_trans_int;


//  store divisor and hold-time settings
//  ------------------------------------
always @(posedge reset or posedge clk)
   begin : process_3
   if (reset == 1'b 1)
      begin
      reg_divisor     <= MDIO_CLK_DIV;	
      reg_holdtime    <= 3'b 000;
      reg_ena45       <= 1'b 1;
      dis_pre         <= 1'b 0;
      end
   else
      begin
        //  store data in register 0 
      if (reg_wr==1'b 1 & mdio_addr == 2'b 00)
         begin
         reg_divisor  <= mdio_data_in[15:7];	
         reg_ena45    <= mdio_data_in[6];
         dis_pre      <= mdio_data_in[5];
         reg_holdtime <= mdio_data_in[4:2];	
         end
      end
   end

//  store data into shifter
//  -----------------------

assign reg_data_in[31:30] = reg_ena45==1'b 1 ? 2'b 00 : 2'b 01; // Clause 45 ST // Clause 22 ST
assign reg_data_in[29:28] = op_code;    // read_t==1'b 1 ? 2'b 10 : 2'b 01;    // Clause 22 OP
assign reg_data_in[27:18] = mdio_addr[1]==1'b 1 ? reg_mmfr[27:18] : mdio_data_in[9:0];  // PHY addr, register addr; Avoid overwriting it, when writing the data register.
assign reg_data_in[17:16] = 2'b 10;     // TA
assign reg_data_in[15:0]  = mdio_data_in;       // data

// read mux
// --------
assign mdio_data_out = (mdio_addr == 2'b 00) ? {reg_divisor, reg_ena45, dis_pre, reg_holdtime, reg_mmfr[16], busy_trans_int} : 
	               (mdio_addr == 2'b 01) ? {read_t, read_p, 4'b 0000, reg_mmfr[27:18]} :
	               reg_mmfr[15:0];

endmodule // module top_mdio_xl
